#ifndef __OgreAML_MeshDefinition_H__
#define __OgreAML_MeshDefinition_H__
#pragma once

// Include stuff
#include "OgreAMLPrerequisites.h"
#include <OgreMesh.h>
#include <OgreSharedPtr.h>
#include <OgreIteratorWrappers.h>
#include <OgreDataStream.h>
#include <OgreProgressiveMesh.h>

namespace Ogre
{
	/**
	 * @brief Specialized class to manipulate mesh load settings
	 * @see MeshDefinitionManager, MeshManager, Mesh, Resource
	 */
	class _OgreAMLExport MeshDefinition : public Resource, public ManualResourceLoader
	{
	public:
		/**
		 * @biref Structure to hold mesh manusl LODs
		 */
		struct LodUsage
		{
			/// Value used by to determine when this lod applies
			Real value;
			/// The name of the alternative mesh to use
			String meshName;
		};

		/// Storage over all manual mesh LODs
		typedef vector<LodUsage>::type LodUsageList;
		/// Specialised VectorIterator over all manual mesh LODs
		typedef VectorIterator<LodUsageList> LodUsageListIterator;
		/// Specialised ConstVectorIterator over all manual mesh LODs
		typedef ConstVectorIterator<LodUsageList> LodUsageListConstIterator;

		/// The enumeration of available LOD methods
		enum LodMethod
		{
			/// Indicate whether manual LODs should be used for mesh
			LM_MANUAL,
			/// Indicate wthether progressive mesh should be used
			LM_PROGRESSIVE
		};

	public:
		/**
		 * @brief Standard constructor
		 * @copydoc Resource::Resource
		 * @see
		 *	Resource, Resource::Resource
		 */
		MeshDefinition( ResourceManager* creator,
			const String& name,
			ResourceHandle handle,
			const String& group,
			bool isManual = false,
			ManualResourceLoader* loader = 0 );

		/**
		 * @brief Virtual destructor
		 */
		virtual ~MeshDefinition();

		/**
		 * @brief Sets real file name for the mesh
		 */
		void setMeshFileName( const String& name );

		/**
		 * @brief Retrieve real file name of the mesh
		 * Remarks By default the mesh file name is
		 * the same as name of this resource
		 */
		const String& getMeshFileName() const;
		
		/**
		 * @brief Adds a new manual level-of-detail for the mesh
		 */
		void addLodLevel( Real value, const String& meshName );
		
		/**
		 * Removes all LOD data for mesh
		 */
		void removeLodLevels();

		/**
		 * @brief Retrieves a specialised VectorIterator over all manual mesh LODs
		 */
		LodUsageListIterator getLodUsageListIterator();

		/**
		 * @brief Retrieves a specialised ConstVectorIterator over all manual mesh LODs
		 */
		LodUsageListConstIterator getLodUsageListIterator() const;

		/**
		 * @brief Sets LOD method which will be used for mesh
		 */
		void setLodMethod( LodMethod method );

		/**
		 * @brief Retrieves the current LOD method
		 */
		LodMethod getLodMethod() const;

		/**
		 * Sets the way to determine the number of vertices collapsed per LOD when progressive mesh is used
		 */
		void setProgressiveMeshReductionMethod( ProgressiveMesh::VertexReductionQuota reductionMethod );

		/**
		 * @brief Retrieves the reduction method for progressive mesh
		 */
		ProgressiveMesh::VertexReductionQuota getProgressiveMeshReductionMethod() const;

		/**
		 * @brief Sets the reduction value.
		 * @remarks Meaning depends on progressive reduction method,
		 *	typically either the proportion of remaining vertices to
		 *	collapse or a fixed number of vertices
		 */
		void setReductionValue( Real value );

		/**
		 * @brief Retrieves the reduction values
		 */
		Real getReductionValue() const;

		/**
		 * @brief Sets a list of lod values indicating the values at which new lods should be generated
		 */
		void setProgressiveLodValueList( const Mesh::LodValueList& lodValues );
		
		/**
		 * @brief Retrieves a list of lod values
		 */
		const Mesh::LodValueList& getProgressiveLodValueList() const;

		/**
		 * @brief Sets whether LOD meshes should be loaded before parent mesh
		 */
		void setPreloadLodMeshes( bool value );

		/**
		 * @brief Returns whether LOD meshes should be loaded before parent mesh
		 */
		bool getIsPreloadLodMeshes() const;

	protected:
		/**
		 * @brief Overridden from Resource
		 * @copydoc Resource::loadImpl
		 * @see
		 *	Ogre::Resource
		 */
		void loadImpl();

		/**
		 * @brief Overridden from Resource
		 * @copydoc Resource::unloadImpl
		 * @see
		 *	Resource
		 */
		void unloadImpl();

		/**
		 * @brief Overridden from Resource
		 * @copydoc Resource::calculateSize
		 * @see
		 *	Resource
		 */
		size_t calculateSize() const;

	public:
		/**
		 * @brief Overridden from ManualResourceLoader
		 * @copydoc ManualResourceLoader::prepareResource
		 * @see
		 *	ManualResourceLoader
		 */
		void prepareResource(Resource* resource);

		/**
		 * @brief Overridden from ManualResourceLoader
		 * @copydoc ManualResourceLoader::loadResource
		 * @see
		 *	ManualResourceLoader
		 */
		void loadResource(Resource* resource);

	protected:
		/// Real file name of the mesh
		String mMeshFileName;
		/// Central list over all manual mesh LODs
		LodUsageList mLodUsageList;
		/// The preloaded data stream
		DataStreamPtr mFreshFromDisk;
		/// The LOD method which will be used for mesh
		LodMethod mLodMethod;
		/// The way to determine the number of vertices collapsed per LOD
		ProgressiveMesh::VertexReductionQuota mReductionMethod;
		/// Progressive reduction value 
		Real mReductionValue;
		/// A list of lod values indicating the values at which new lods should be generated
		Mesh::LodValueList mProgressiveLodValueList;
		/// Indicate whether LODs meshes should be loaded before parent mesh, default true
		bool mPreloadLodMeshes;
	};
	
	
	/**
	 * @brief Specialisation of SharedPtr to allow SharedPtr to be assigned to MeshDefinitionPtr
	 * @note Has to be a subclass since we need operator=.
	 * We could templatise this instead of repeating per Resource subclass,
	 * except to do so requires a form VC6 does not support i.e.
	 * ResourceSubclassPtr<T> : public SharedPtr<T>
	 */
	class _OgreAMLExport MeshDefinitionPtr : public SharedPtr<MeshDefinition>
	{
	public:
		MeshDefinitionPtr() : SharedPtr<MeshDefinition>() {}
		explicit MeshDefinitionPtr(MeshDefinition* rep) : SharedPtr<MeshDefinition>(rep) {}
		MeshDefinitionPtr(const MeshDefinitionPtr& r) : SharedPtr<MeshDefinition>(r) {} 
		MeshDefinitionPtr(const ResourcePtr& r) : SharedPtr<MeshDefinition>()
		{
			// lock & copy other mutex pointer
			OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
			{
				OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
				OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
				pRep = static_cast<MeshDefinition*>(r.getPointer());
				pUseCount = r.useCountPointer();
				if (pUseCount)
				{
					++(*pUseCount);
				}
			}
		}
		
		/// Operator used to convert a ResourcePtr to a MeshDefinitionPtr
		MeshDefinitionPtr& operator=(const ResourcePtr& r)
		{
			if (pRep == static_cast<MeshDefinition*>(r.getPointer()))
				return *this;
			
			release();
			// lock & copy other mutex pointer
			OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
			{
				OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
				OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
				pRep = static_cast<MeshDefinition*>(r.getPointer());
				pUseCount = r.useCountPointer();
				if (pUseCount)
				{
					++(*pUseCount);
				}
			}
			else
			{
				// RHS must be a null pointer
				assert(r.isNull() && "RHS must be null if it has no mutex!");
				setNull();
			}
			return *this;
		}
	};

} // end of namespace Ogre

#endif // include guard, end of #ifndef __OgreAML_MeshDefinition_H__
